


#include "EasyXMen_asn1.h"

/*
 * Encode length field in ASN.1 DER encoding to byte stream
 * len: length value to encode
 * out: pointer to output buffer pointer
 * outlen: output length
 * return: 1 on success, -1 on failure
 */

int EasyXmen_SM2_Asn1_Len2Der(uint32_t len, uint8_t** out, uint32_t* outlen)
{
    uint8_t buf[4];
    int nbytes;

    /* Parameter validation */
    if (len > XMEN_INT_MAX || !outlen) {
        return -1;
    }

    /* Short form: length < 128, use single byte */
    if (len < 128u)
    {
        if (out && *out)
        {
            *(*out)++ = (uint8_t)len;
        }
        (*outlen)++;
    }
    else
    {
        /* Long form: calculate number of bytes needed */
        if (len < 256u)
        {
            nbytes = 1;
        }
        else if (len < 65536u)
        {
            nbytes = 2;
        }
        else if (len < (1 << 24))
        {
            nbytes = 3;
        }
        else
        {
            nbytes = 4;
        }

        /* Convert length to big-endian bytes */
        XMEN_PUTU32(buf, (uint32_t)len);

        /* Write length bytes if output buffer provided */
        if (out && *out) {
            *(*out)++ = 0x80 + nbytes;  /* First byte: 0x80 + number of length bytes */
            memcpy(*out, buf + 4 - nbytes, nbytes);  /* Copy length bytes */
            (*out) += nbytes;
        }
        (*outlen) += 1 + nbytes;
    }

    /* Success */
    return 1;
}

/**
 * @description Decodes the length of an ASN.1 DER-encoded value.
 *        This function takes a DER-encoded byte stream and extracts the length of the next value.
 *        It handles both short and long form lengths according to the DER encoding rules.
 *        The function validates the input parameters and checks for consistency in the length encoding.
 * @param[out] len  : Pointer to a variable where the decoded length will be stored.
 * @param[in] in    : Pointer to a pointer to the DER-encoded byte stream.
 * @param[in] inlen : Pointer to the length of the DER-encoded byte stream.
 * @return int      : Returns a status code indicating the result of the operation.
 * @retval  1       : Length successfully decoded.
 * @retval -1       : Invalid input parameters or invalid length encoding.
 * @retval -2       : Insufficient remaining input length.
 */
int EasyXmen_SM2_Asn1_Der2Len(uint32_t* len, const uint8_t** in, uint32_t* inlen)
{
    uint8_t buf[4] = { 0 };

    /* Parameter validation */
    if (!len || !in || !(*in) || !inlen || (*inlen == 0u)) {
        return -1;
    }

    /* Handle short form (length < 128) */
    if (**in < 128) {
        *len = *(*in)++;
        (*inlen)--;
    } else {
        /* Long form: get number of length bytes */
        int nbytes = *(*in)++ & 0x7f;
        (*inlen)--;

        /* Validate number of bytes */
        if (nbytes < 1 || nbytes > 4 || *inlen < (uint32_t)nbytes) {
            return -1;
        }

        /* Read length bytes into buffer and convert from big-endian */
        memcpy(buf + 4 - nbytes, *in, nbytes);
        *len = (uint32_t)XMEN_GETU32(buf);
        *in += nbytes;
        *inlen -= nbytes;
    }

    /* Ensure remaining input is long enough */
    if (*inlen < *len) {
        return -2;
    }

    /* Success */
    return 1;
}

/*
 * Encode ASN.1 header with tag and length
 *
 * @param[in] tag     ASN.1 tag value
 * @param[in] dlen    Length of data that follows
 * @param[out] out    Output buffer to write encoded header, NULL for length only
 * @param[out] outlen Length of encoded output
 * @return 1 on success, -1 on error
 */

int EasyXmen_SM2_Asn1_Head2Der(int tag, uint32_t dlen, uint8_t** out, uint32_t* outlen)
{
	if (!outlen) {
		return -1;
	}

	if (out && *out) {
		*(*out)++ = (uint8_t)tag;
	}
	(*outlen)++;

	(void)EasyXmen_SM2_Asn1_Len2Der(dlen, out, outlen);
	return 1;
}


/*
 * Decode ASN.1 type with tag, length and data
 *
 * @param[in] tag     Expected ASN.1 tag value
 * @param[out] d      Pointer to decoded data
 * @param[out] dlen   Length of decoded data
 * @param[in,out] in  Input buffer to decode from, updated to point after decoded data
 * @param[in,out] inlen Length of remaining input, updated after decoding
 * @return 1 on success, 0 if tag doesn't match, -1 on error
 */

int EasyXmen_SM2_Asn1_Der2Type(int tag, const uint8_t** d, uint32_t* dlen, const uint8_t** in, uint32_t* inlen)
{
	/* Validate parameters */
	if (!d || !dlen || !in || !(*in) || !inlen) {
		return -1;
	}

	/* Check tag matches */
	if (*inlen == 0u || **in != tag) {
		*d = NULL;
		*dlen = 0;
		return 0;
	}
	(*in)++;
	(*inlen)--;

	/* Decode length */
	if (EasyXmen_SM2_Asn1_Der2Len(dlen, in, inlen) != 1) {
		return -1;
	}

	/* Set data pointer and update input buffer */
	*d = *in;
	*in += *dlen;
	*inlen -= *dlen;
	return 1;
}


/*
 * Encode ASN.1 INTEGER to DER encoding with custom tag
 *
 * @param[in] tag     ASN.1 tag value to use
 * @param[in] a       Integer value to encode as byte array
 * @param[in] alen    Length of integer byte array
 * @param[out] out    Output buffer to write encoded data, NULL for length only
 * @param[out] outlen Length of encoded output
 * @return 1 on success, 0 if no data provided, -1 on error
 */

int EasyXmen_SM2_Asn1_Int2Der(int tag, const uint8_t* a, uint32_t alen, uint8_t** out, uint32_t* outlen)
{
    /* Parameter validation */
	if (!outlen) {
		return -1;
	}

    /* Handle NULL data case */
	if (!a) {
		return 0;
	}

    /* Validate length */
	if (alen <= 0u || alen > XMEN_INT_MAX) {
		return -1;
	}

    /* Write tag byte */
	if (out && *out) {
		*(*out)++ = tag;
	}
	(*outlen)++;

    /* Skip leading zeros */
	while (*a == 0 && alen > 1u) {
		a++;
		alen--;
	}

    /* Handle sign bit */
	if (a[0] & 0x80) {
        /* Add leading zero byte to preserve sign */
		EasyXmen_SM2_Asn1_Len2Der(alen + 1u, out, outlen);
		if (out && *out) {
			*(*out)++ = 0x00;
			memcpy(*out, a, alen);
			(*out) += alen;
		}
		(*outlen) += 1 + alen;
	}
	else {
        /* Write length and data directly */
		EasyXmen_SM2_Asn1_Len2Der(alen, out, outlen);
		if (out && *out) {
			memcpy(*out, a, alen);
			(*out) += alen;
		}
		(*outlen) += alen;
	}

    /* Success */
	return 1;
}

/*
 * Decode ASN.1 INTEGER from DER encoding with custom tag
 *
 * @param[in] tag     Expected ASN.1 tag value
 * @param[out] a      Pointer to store decoded integer value
 * @param[out] alen   Length of decoded integer value
 * @param[in,out] in  Input buffer containing DER encoded data
 * @param[in,out] inlen Length of input buffer, updated with remaining bytes
 * @return 1 on success, 0 if no data, -1 on error
 */

int EasyXmen_SM2_Asn1_Der2Int(int tag, const uint8_t** a, uint32_t* alen, const uint8_t** in, uint32_t* inlen)
{
	uint32_t len;

    /* Parameter validation */
	if (!a || !alen || !in || !(*in) || !inlen) {
		return -1;
	}

    /* Check tag */
	if ((*inlen == 0u) || (**in != tag)) {
		*a = NULL;
		*alen = 0;
		return 0;
	}
	(*in)++;
	(*inlen)--;

    /* Get length */
	if (EasyXmen_SM2_Asn1_Der2Len(&len, in, inlen) != 1) {
		return -1;
	}
	if (len == 0u) {
		return -1;
	}

    /* Check sign bit */
	if (**in & 0x80) {
		return -1;
	}

    /* Skip redundant leading zero */
	if (**in == 0u && len > 1u) {
		(*in)++;
		(*inlen)--;
		len--;

		if (((**in) & 0x80) == 0) {
			return -1;
		}
	}

    /* Validate leading zeros */
	if (**in == 0u && len > 1u) {
		return -1;
	}

    /* Set output parameters */
	*a = *in;
	*alen = len;
	*in += len;
	*inlen -= len;

    /* Success */
	return 1;
}

/*
 * Check if length is zero
 *
 * @param[in] len    Length value to check
 * @return 1 if length is zero, -1 if non-zero
 */

int EasyXmen_SM2_Asn1_LenIsZero(uint32_t len)
{
	if (len) {
		return -1;
	}
	return 1;
}

/*
 * Check if first length is less than or equal to second length
 *
 * @param[in] len1   First length to compare
 * @param[in] len2   Second length to compare
 * @return 1 if len1 <= len2, -1 if len1 > len2
 */

int EasyXmen_SM2_Asn1_LenCmp(uint32_t len1, uint32_t len2)
{
	if (len1 > len2) {
		return -1;
	}
	return 1;
}

